#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import json
import lxml.etree
import psutil
import shlex
import subprocess
from mc_util import McUtil
from mc_util import UnixDomainSocketApiServer
from mc_param import McConst


class McMirrorSiteFactoryRunner(UnixDomainSocketApiServer):

    def __init__(self, param):
        self.param = param
        self.objDict = dict()                       # <factory-id,factory-object>
        self.sockDict = dict()                      # <factory-id,sock>
        super().__init__(McConst.factoryApiSocket, self._clientAppearFunc, self._clientDisappearFunc, self._clientNoitfyFunc)

    def dispose(self):
        super().dispose()
        for obj in self.objDict.values():
            obj.dispose()

    def addFactory(self, factoryId):
        assert factoryId not in self.objDict
        self.objDict[factoryId] = _OneFactory(self.param.mirrorSiteFactoryDict[factoryId])

    def removeFactory(self, factoryId):
        assert factoryId in self.objDict
        assert factoryId not in self.sockDict
        self.objDict[factoryId].dispose()
        del self.objDict[factoryId]

    def _clientAppearFunc(self, sock):
        pid = McUtil.getUnixDomainSocketPeerInfo(sock)[0]
        pid = psutil.Process(pid).ppid()        # get the process id of the shell, which is recorded in updater object
        for factoryId, obj in self.objDict.items():
            if pid == obj.proc.pid:
                self.sockDict[factoryId] = sock
                return factoryId
        raise Exception("client not found")

    def _clientDisappearFunc(self, factoryId):
        del self.sockDict[factoryId]
        self.__removeAllMirrorSitesOfFactory(factoryId)

    def _clientNoitfyFunc(self, factoryId, data):
        if "message" not in data:
            raise Exception("\"message\" field does not exist in notification")
        if "data" not in data:
            raise Exception("\"data\" field does not exist in notification")

        if data["message"] == "add-mirror-site":
            if "metadata" not in data["data"]:
                raise Exception("\"data.metadata\" field does not exist in \"add-mirror-site\" notification")
            if "cfg" not in data["data"]:
                raise Exception("\"data.cfg\" field does not exist in \"add-mirror-site\" notification")
            self.__addMirrorSite(factoryId, data["data"]["metadata"], data["data"]["cfg"])
            return

        if data["message"] == "remove-mirror-site":
            if "mirror-site-id" not in data["data"]:
                raise Exception("\"data.mirror-site-id\" field does not exist in \"remove-mirror-site\" notification")
            self.__removeMirrorSite(factoryId, data["data"]["mirror-site-id"])
            return

        if data["message"] == "error":
            if "exc_info" not in data["data"]:
                raise Exception("\"data.exc_info\" field does not exist in \"error\" notification")
            # FIXME
            assert False

        raise Exception("message type \"%s\" is not supported" % (data["message"]))

    def __addMirrorSite(self, factoryId, metadataXml, cfgJson):
        rootElem = lxml.etree.fromstring(metadataXml)
        if cfgJson == "":
            cfgDict = dict()
        else:
            cfgDict = json.loads(cfgJson)

        msId = self.param.globalDataManager.addMirrorSite(self.param.mirrorSiteFactoryDict[factoryId].pluginDir, rootElem, cfgDict, factoryId)
        self.param.updater.addMirrorSite(msId)

    def __removeMirrorSite(self, factoryId, msId):
        if msId in self.param.mirrorSiteDict:
            self.param.updater.removeMirrorSite(msId)
            self.param.globalDataManager.removeMirrorSite(msId)

    def __removeAllMirrorSitesOfFactory(self, factoryId):
        for msId, msObj in self.param.mirrorSiteDict.items():
            if msObj.factoryId != factoryId:
                continue
            self.param.updater.removeMirrorSite(msId)
            self.param.globalDataManager.removeMirrorSite(msId)


class _OneFactory:

    def __init__(self, factoryObj):
        self.factoryObj = factoryObj

        # create process
        cmd = shlex.join([
            self.factoryObj.runnerExe,
            json.dumps({
                "id": self.factoryObj.id,
                "config": self.factoryObj.cfgDict,
            }),
        ])
        cmd += " 2>&1 | "
        cmd += shlex.join(McUtil.rotateLogCmdList(self.factoryObj.mainLogFile, McConst.rotateLogFileSize, McConst.rotateLogFileCount))
        self.proc = subprocess.Popen(cmd, shell=True)

    def dispose(self):
        if self.proc is not None:
            if self.proc.poll() is None:
                self.proc.terminate()
            self.proc.wait()
